package secrets

import (
	"fmt"
	"os"
	"strings"

	"apiote.xyz/p/eeze/crypto"
	"apiote.xyz/p/eeze/fs"

	"apiote.xyz/p/go-dirty"
	"git.sr.ht/~sircmpwn/go-bare"
)

type Secret map[string]SecretEntry
type SecretEntry struct {
	Hidden bool
	Value  string
}

type Entry struct {
	ID       string
	Label    string
	Username string
}

func (secret Secret) write(key []byte, id SecretEntry) error {
	dataHome := fs.DataHome()
	plaintext, err := bare.Marshal(&secret)
	if err != nil {
		return fmt.Errorf("while marshalling secret: %s", err)
	}
	ciphertext, err := crypto.Encrypt(plaintext, key)
	if err != nil {
		return fmt.Errorf("while encrypting secret: %s", err)
	}
	os.WriteFile(dataHome+"/secrets/"+id.Value, ciphertext, 0644)
	return nil
}

func (secret Secret) delete() error {
	dataHome := fs.DataHome()
	id := secret["id"]
	return os.Remove(dataHome + "/secrets/" + id.Value)
}

func Get(ids []string, key Key) ([]Secret, error) {
	secrets := []Secret{}
	for _, id := range ids {
		ciphertext, err := os.ReadFile(fs.DataHome() + "/secrets/" + id)
		if err != nil {
			return secrets, fmt.Errorf("while reading secret %s: %w", id, err)
		}
		plaintext, err := crypto.Decrypt(ciphertext, key.bytes)
		if err != nil {
			return secrets, fmt.Errorf("while decrypting secret %s: %w", id, err)
		}
		secret := Secret{}
		err = bare.Unmarshal(plaintext, &secret)
		if err != nil {
			return secrets, fmt.Errorf("while unmarshalling secret %s: %w", id, err)
		}
		secret["id"] = SecretEntry{
			Value:  id,
			Hidden: false,
		}
		secrets = append(secrets, secret)
	}
	return secrets, nil
}

func Delete(id string, key Key) error {
	index, err := openIndex(key.bytes)
	if err != nil {
		return fmt.Errorf("while opening index: %s", err)
	}
	secrets, err := Get([]string{id}, key)
	if err != nil {
		return fmt.Errorf("while getting secret: %s", err)
	}
	secret := secrets[0]
	index.remove(secret, secret["id"])
	index.write(key.bytes)
	secret.delete()

	return nil
}

func Add(key Key) error {
	secrets := []Secret{}
	err := dirty.LoadStruct(os.Stdin, &secrets)
	if err != nil {
		return fmt.Errorf("while unmarshalling secrets: %s", err)
	}
	var (
		id SecretEntry
		ok bool
	)
	index, err := openIndex(key.bytes)
	if err != nil {
		return fmt.Errorf("while opening index: %s", err)
	}
	oldSecret := Secret{}
	for _, secret := range secrets {
		if id, ok = secret["id"]; ok && id.Value == "" || !ok {
			id.Value, err = crypto.MakeID()
			if err != nil {
				return fmt.Errorf("while creating ID: %s", err)
			}
		} else {
			oldSecrets, err := Get([]string{id.Value}, key)
			if err != nil {
				return fmt.Errorf("while getting old secret: %s", err)
			}
			oldSecret = oldSecrets[0]
			index.remove(oldSecret, id)
		}
		delete(secret, "id")
		err = secret.write(key.bytes, id)
		if err != nil {
			return fmt.Errorf("while writing secret: %s", err)
		}
		index.add(secret, id)
	}
	err = index.write(key.bytes)
	if err != nil {
		return fmt.Errorf("while writing index: %s", err)
	}
	return nil
}

func Filter(id, label, url, username string, key Key) ([]string, error) {
	ids := []string{}
	if id != "" {
		ids = append(ids, id)
	} else {
		index, err := openIndex(key.bytes)
		if err != nil {
			return ids, fmt.Errorf("while opening index: %w", err)
		}
		potentialIds := map[string][]string{}
		if label != "" {
			potentialIds = index.LabelIndex[label]
		} else if url != "" {
			potentialIds = index.URLIndex[url]
		}

		if username != "" {
			ids = append(ids, potentialIds[username]...)
		} else {
			for _, usernameIds := range potentialIds {
				ids = append(ids, usernameIds...)
			}
		}
	}
	return ids, nil
}

func Format(secrets []Secret, _fields []string, export bool, prettyPrint bool) string {
	result := ""
	fields := _fields
	if export {
		result += "(\n"
	}
	secretsLen := len(secrets)
	for i, secret := range secrets {
		if export && len(fields) == 0 {
			for key := range secret {
				fields = append(fields, key)
			}
		}
		if export {
			result += "\t(\n"
		}
		for _, field := range fields {
			if export {
				val := secret[field].Value
				if strings.Contains(val, "\n") {
					result += fmt.Sprintf("\t\t('%s' (('value' `\n%s`) ('hidden' %v)))\n", field, val, secret[field].Hidden)
				} else {
					val = strings.ReplaceAll(val, "\\", "\\\\")
					val = strings.ReplaceAll(val, "'", "\\'")
					result += fmt.Sprintf("\t\t('%s' (('value' '%s') ('hidden' %v)))\n", field, val, secret[field].Hidden)
				}
			} else if prettyPrint {
				if secret[field].Hidden {
					result += fmt.Sprintf("[H] ")
				}
				result += fmt.Sprintf("%s: %s\n", field, secret[field].Value)
			} else {
				result += secret[field].Value + "\n"
			}
		}
		if export {
			result += "\t)\n"
		} else if i+1 < secretsLen {
			result += "---\n"
		}
		fields = _fields
	}
	if export {
		result += ")\n"
	}
	return result
}

func List(key Key) ([]Entry, error) {
	entries := []Entry{}
	index, err := openIndex(key.bytes)
	if err != nil {
		return entries, fmt.Errorf("while opening index: %w", err)
	}
	for label, secrets := range index.LabelIndex {
		for username, ids := range secrets {
			for _, id := range ids {
				entry := Entry{
					ID:       id,
					Label:    label,
					Username: username,
				}
				entries = append(entries, entry)
			}
		}
	}
	return entries, nil
}
